24章 リファクタリング
24.3.3 qst exe.icon
24.0 概要
神話:よく管理されたソフトウェアプロジェクトでは、整然と要求が策定され、不変のプログラム役割リストが作成され、要求に従って設計が行われる。この作業は、コーディングが最初から最後まで一直線に進むように、つまりほとんどのコードが一度だけ書かれ、テストされ、後は忘れてもかまわないように、慎重に進められる。
実際には、最初の開発の時点で頻繁に書き換えが行われる コーディング、デバッグ、単体テストがプロジェクト全体の30~65%を占めるのが証拠
現代の開発プラクティスはコンストラクション段階でコードを変更する可能性が高い
24.1 ソフトウェアの進化の種類
ソフトウェアの進化にはプラスもマイナスもある
品質が向上するか低下するか
24.1.1 ソフトウェアの進化の哲学
コードが大きいとか、ねじれているとか、複雑だとかいうことは、保守でひどいコードになることに比べれば何でもない。
ソフトウェアの進化の鉄則は、「進化によってプログラムの内部品質が改善される」ということだ。
コードを良くしようと思ったときにも上の観点を持つことが大事
24.2 リファクタリング概論
24.2.1 リファクタリングする理由
においがするかどうか
引っかかるのがあったら教えて下さい
コードが重複している
ルーチンが長過ぎる
12000行のルーチンを4000行にまで減らした話も
ループが長すぎる、またはネストが深すぎる
クラスのモジュール凝集度が小さい
クラスのインターフェイスが一貫性のある抽象化を実現していない
引数リストの数が多すぎる
変更がクラス内部の他の部分に影響しない
変更が発生したら複数のクラスを同時に修正しなければならない
継承の階層を並行して修正しなければならない
case文を並行して修正しなければならない
一緒に使用する関連データがクラスにまとめられていない
ルーチンがそのクラスの機能よりも別のクラスの機能を多く使用する
基本データ型をオーバーロードしている
クラスがほとんど何もしない
ルーチン間を「トランプデータ」が流れる
他のルーチンにデータを渡すだけのデータ
特定のデータを渡すことに各ルーチンのインターフェイスに一貫性があればOK
中間オブジェクトが何もしていない
ルーチンの名前が不適切
データメンバがパブリック
サブクラスがスーパークラスのルーチンをほんの一部しか使用しない
複雑なコードを説明するためにコメントが使われている
グローバル変数を使用している
ルーチンの呼び出し前に初期化コードが使用され、呼び出し後に後処理コードが使用されている
プログラムに先のことを考えたコードが含まれている
未来予測が下手で、ろくでもない結果になることが多い
YAGNIの法則
24.2.2 リファクタリングしない理由
基本的にはコードの変更は全てリファクタリング
コード修正するときには次の節を見てねということかな
24.3 リファクタリングの詳細
リファクタリングに関するTips
JetBrainsのリファクタリング機能がとても便利だった気がする
24.3.1 データレベルのリファクタリング
フレームワークがよしなにやってくれてるものもあり
マジックナンバーを名前付きの定数に置き換える
変数名をより明白なものにするか、その目的がわかりやすいものに変える
式をインラインにする
式をルーチンに置き換える
中間変数を導入する
多目的変数を複数の単一目的変数に変換する
ローカルの目的には引数ではなくローカル変数を使用する
データプリミティブをクラスに変換する
一連のコードをクラスまたは列挙に変換する
一連の型コードをスーパークラスとサブクラスに変換する
配列をオブジェクトに変換する
コレクションをカプセル化する
従来のレコードをデータクラスに置き換える
24.3.2 ステートメントレベルのリファクタリング
論理式を分解する
複雑な論理式をわかりやすい名前の付いた論理関数にする
条件式に分散している重複するコードを1つにまとめる
ループ制御変数ではなくbreakやreturnを使用する
早期returnする
条件文、特にcase文の繰り返しをポリモーフィズムで書き換える
null値を評価するのではなくnullオブジェクトを生成して使用する
Customerクラスにおいて名前が確認されていない顧客を「不明」とするなど
24.3.3 ルーチンレベルのリファクタリング
ルーチンやメソッドを抽出する
ルーチンのコードをインラインにする
長いルーチンをクラスに変換する
複雑なアルゴリズムを単純なアルゴリズムで代用する
引数を追加・削除する
照会と変更を分離する
コマンドクエリパターン
同じようなルーチンは引数を介在させてまとめる
入力引数によって振る舞いの異なるルーチンを分離する
特定のフィールドではなくオブジェクト全体を渡す
同じオブジェクト内の複数のフィールドを渡してる場合
オブジェクト全体ではなく特定のフィールドを渡す
フィールドを渡すためだけにオブジェクトを作っている場合
ダウンキャストをカプセル化する
リファクタリングの本を読んだ方が良さそう
Linux Performance Analysis in 60,000 Milliseconds
Linuxサーバにログインしたらいつもやっているオペレーション
24.3.4moch5oMaki.icon
24.3.4 クラス実装のリファクタリング
クラスレベルでコードを改良するためのテクニック(Tips)の紹介
値オブジェクトを参照オブジェクトに変更する
参照オブジェクトを値オブジェクトに変更する
仮想ルーチンをデータの初期化に置き換える
メンバルーチンまたはメンバデータの配置を変更する
継承階層全体の変更を検討することで重複を排除する
スーパークラス→サブクラス、サブクラス→スーパークラスそれぞれ適切な場所へ移動する
ルーチンの移動
フィールドの移動
コンストラクタの本体の移動
特殊なコードをサブクラスとして抽出する
同じようなコードをスーパークラスにまとめる
24.3.5 クラスインターフェイスのリファクタリング
クラスのインターフェイスに関連するリファクタリングの手法
クラスのレベルになってくると凝集度とかを考えてリファクタリングしていくことになるので、コードの機能や意味的なつながりを意識する必要が出てくる。重複の排除とかは結構機械的にできるイメージだけど、このへんの判断が難しいmoch5oMaki.icon
ルーチンを別のクラスに移動する
1つのクラスを2つに分ける
クラスを削除する
委譲を隠ぺいする
クラスAから別のクラスBとCを両方とも直接呼び出すのが妥当なのかを検討すること。クラスBに処理を委譲してクラスBからクラスCを呼び出すことを検討する。
中間オブジェクトを削除する
継承を委譲に置き換える
外部ルーチンを導入する
拡張クラスを導入する
もとのクラスをサブクラス化して新しいルーチンを追加するか、クラスをラッピングして必要なルーチンを公開する
公開されているメンバ変数をカプセル化する
メンバデータをプライベートに変更して値をルーチン経由で公開する
変更できないフィールドのSet()ルーチンを削除する
コンストラクタを使って初期化する
クラスの外側で使用されないルーチンを隠ぺいする
使用されないルーチンをカプセル化する
スーパークラスとサブクラスの実装が非常によく似ている場合は1つにまとめる
24.3.6 システムレベルのリファクタリング
システム全体のレベルでのリファクタリング
制御できないデータについては、最も信頼のおけるデータソースを作成する
一方向のクラス結合を双方向のクラス結合に変更する
単純なコンストラクタではなくファクトリメソッドを提供する
エラーコードを例外に置き換える、または例外をエラーコードに置き換える
24.4 安全なリファクタリング
リファクタリングは、コードの品質を向上させるための強力な手段である。強力なツールの常として、リファクタリングは使い方を誤れば改善どころか害をもたらすことがある。
左端のコラム?の引用もなかなかに激しい比喩moch5oMaki.icon
動いているシステムを切開するのは、人間の脳を切開して神経を交換するようなものだ。
ということで安全にリファクタリングしましょうねという話。そもそもIDEの機能で補完してくれるならそれを使うとかも取り入れたい。
くどいけど、リファクタリングの本だとスモールステップでコード例とともに解説されているので、このあたりのステップは特に分かりやすいです。
レガシーコード改善ガイド
最初のコードを保存する
リファクタリングの規模を小さく保つ
1箇所触っているうちに他の場所が気になりがちmoch5oMaki.icon
リファクタリングは一度に1つずつ行う
コミット単位で考えながらやるとできるようになるかも?という気持ち tommy.icon
git add -p 使ってますか?
使いすぎも良くないやつ tommy.icon
手順をリストアップする
そういえばTDDでも最初にToDoリストを作るというステップがある
「駐車スペース」を設ける
日本語だとイマイチしっくりこないけど、Parkingと考えると高速道路のパーキング=休憩所的なイメージ??moch5oMaki.icon
チェックポイントをこまめに作成する
コンパイラの警告を利用する
再テストする
テストケースを追加する
これが大事ですよね
変更をレビューする
数行の変更や小さな変更を軽視してはいけないという教訓
リファクタリングの危険度に応じて手法を調整する
データベーススキーマの変更や論理評価の変更などを行うリファクタリングは特に危険
rubocopにifの書き方を怒られたとき、rubocopの指示通りunlessとか後置ifとかを駆使してRubyぽく書こうとして撃沈した思い出があります。後置if使ったガード節とか、慣れてなくて頭が混乱しましたmoch5oMaki.icon
24.4.1 リファクタリングに適さない状況
コードや修正の埋め合わせとしてリファクタリングを使用してはならない
動くコードの変更のことだけをリファクタリングと呼ぶ
機能を部分的に書いておいて、後からリファクタリングで完成させようと考えてはならない。
リファクタリングをコードの書き直しにしない
大きなリファクタリングは惨事のもとである。
24.5 リファクタリング戦略
リファクタリングも収益逓減の法則の対象となり、「80対20の法則」が適用される。利益の80%をもたらす20%のリファクタリングに時間を使うべきである。
ルーチンを追加するときにリファクタリングする
クラスを追加するときにリファクタリングする
エラーを修正するときにリファクタリングする
エラーが発生しやすいモジュールに的を絞る
複雑なモジュールに的を絞る
保守環境で触った部分を改良する
ボーイスカウト・ルール
「リファクタリング」では三度目の法則(3ストライクでリファクタリング開始)として紹介されていた(p.51)
理想的なコードと乱雑なコードの間のインターフェイスを定義して、コードにインターフェイスを横断させる
複雑な現実世界と、理想の世界の間に乱雑さを吸収する層をつくるイメージ
運用のコードを改善する方法の1つは、できの悪いレガシーコードで触った部分をリファクタリングして、「乱雑な現実世界へのインターフェース」の向こう側へ移動することだ
24.6 参考資料
24.7 まとめ
以下引用
プログラムの変更は、最初の開発時と最初のリリース後の両方において。避けがたい事実である
ソフトウェアは、変更によって改良されることもあれば、後退することもある。コードの進化に伴って内部品質を改善することが、ソフトウェアの進化の鉄則である。
リファクタリングを成功させる1つの鍵は、リファクタリングの必要性を示すさまざまな警告のサインやにおいに注意することだ。
リファクタリングを成功させるもう1つの鍵は、さまざまなリファクタリングの方法を知ることである。
リファクタリングを成功させる最後の鍵は、安全確実にリファクタリングを行うための戦略を練ることである。状況に適したリファクタリング手法を選択する。
開発時のリファクタリングは、プログラムを改善し、最初に済ませておきたいすべての変更を行う絶好のチャンスである。開発中のこうした機会を逃さないようにする。